package org.offsync.async

import java.net.SocketAddress
import java.net.SocketOption
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousChannelGroup
import java.nio.channels.AsynchronousSocketChannel
import java.nio.channels.CompletionHandler
import java.util.concurrent.TimeUnit

class AsyncSocket(underlying: AsynchronousSocketChannel) {
  def bind(address: SocketAddress) = underlying.bind(address)

  def connect[A](remote: SocketAddress, attachment: A)(f: (() => Void, A) => Unit): Unit = {
    underlying.connect(remote, attachment, new CompletionHandler[Void, A] {
      def completed(result: Void, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def connect(remote: SocketAddress)(f: (() => Void) => Unit): Unit = {
    underlying.connect(remote, null, new CompletionHandler[Void, Null] {
      def completed(result: Void, attachment: Null): Unit = f {() => result} 
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }

  def getRemoteAddress() = underlying.getRemoteAddress()
  
  def provider() = underlying.provider()
  
  def read[A](
      dsts: Array[ByteBuffer], offset: Integer, length: Integer,
      timeout: Long, unit: TimeUnit, attachment: A)(f: (() => Long, A) => Unit): Unit = {
    underlying.read(dsts, offset, length, timeout, unit, attachment, new CompletionHandler[java.lang.Long, A] {
      def completed(result: java.lang.Long, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def read(
      dsts: Array[ByteBuffer], offset: Integer, length: Integer,
      timeout: Long, unit: TimeUnit)(f: (() => Long) => Unit): Unit = {
    underlying.read(dsts, offset, length, timeout, unit, null, new CompletionHandler[java.lang.Long, Null] {
      def completed(result: java.lang.Long, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }

  def read[A](dst: ByteBuffer, attachment: A)(f: (() => Integer, A) => Unit): Unit = {
    underlying.read(dst, attachment, new CompletionHandler[Integer, A] {
      def completed(result: Integer, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }
  
  def read(dst: ByteBuffer)(f: (() => Integer) => Unit): Unit = {
    underlying.read(dst, null, new CompletionHandler[Integer, Null] {
      def completed(result: Integer, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }
  
  def read[A](
      dst: ByteBuffer, timeout: Long, unit: TimeUnit, attachment: A)(f: (() => Integer, A) => Unit): Unit = {
    underlying.read(dst, timeout, unit, attachment, new CompletionHandler[Integer, A] {
      def completed(result: Integer, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def read(
      dst: ByteBuffer, timeout: Long, unit: TimeUnit)(f: (() => Integer) => Unit): Unit = {
    underlying.read(dst, timeout, unit, null, new CompletionHandler[Integer, Null] {
      def completed(result: Integer, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }

  def setOption[T](name: SocketOption[T], value: T) = underlying.setOption(name, value)

  def shutdownInput() = underlying.shutdownInput()

  def shutdownOutput() = underlying.shutdownOutput()

  def write[A](
      srcs: Array[ByteBuffer], offset: Integer, length: Integer, timeout: Long, unit: TimeUnit, attachment: A)(f: (() => Long, A) => Unit): Unit = {
    underlying.write(srcs, offset, length, timeout, unit, attachment, new CompletionHandler[java.lang.Long, A] {
      def completed(result: java.lang.Long, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def write(
      srcs: Array[ByteBuffer], offset: Integer, length: Integer, timeout: Long, unit: TimeUnit)(f: (() => Long) => Unit): Unit = {
    underlying.write(srcs, offset, length, timeout, unit, null, new CompletionHandler[java.lang.Long, Null] {
      def completed(result: java.lang.Long, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }

  def write[A](src: ByteBuffer, attachment: A)(f: (() => Integer, A) => Unit): Unit = {
    underlying.write(src, attachment, new CompletionHandler[Integer, A] {
      def completed(result: Integer, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def write(src: ByteBuffer)(f: (() => Integer) => Unit): Unit = {
    underlying.write(src, null, new CompletionHandler[Integer, Null] {
      def completed(result: Integer, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }

  def write[A](src: ByteBuffer, timeout: Long, unit: TimeUnit, attachment: A)(f: (() => Integer, A) => Unit): Unit = {
    underlying.write(src, timeout, unit, attachment, new CompletionHandler[Integer, A] {
      def completed(result: Integer, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def write(src: ByteBuffer, timeout: Long, unit: TimeUnit)(f: (() => Integer) => Unit): Unit = {
    underlying.write(src, timeout, unit, null, new CompletionHandler[Integer, Null] {
      def completed(result: Integer, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }
}

object AsyncSocket {
  def open() = AsynchronousSocketChannel.open()
  
  def open(group: AsynchronousChannelGroup) = AsynchronousSocketChannel.open(group)
}
